/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.mariotaku.popupmenu;

import org.mariotaku.twidere.R;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AbsListView.LayoutParams;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.PopupWindow;

/**
 * A ListPopupWindow anchors itself to a host view and displays a list of
 * choices.
 * 
 * <p>
 * ListPopupWindow contains a number of tricky behaviors surrounding
 * positioning, scrolling parents to fit the dropdown, interacting sanely with
 * the IME if present, and others.
 * 
 * @see android.widget.AutoCompleteTextView
 * @see android.widget.Spinner
 */
public class ListPopupWindowCompat implements ListPopupWindow {
	private static final String TAG = "ListPopupWindow";
	private static final boolean DEBUG = false;

	/**
	 * This value controls the length of time that the user must leave a pointer
	 * down without scrolling to expand the autocomplete dropdown list to cover
	 * the IME.
	 */
	private static final int EXPAND_LIST_TIMEOUT = 250;

	private final Context mContext;
	private final PopupWindow mPopup;
	private ListAdapter mAdapter;
	private DropDownListView mDropDownList;

	private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
	private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
	private int mDropDownHorizontalOffset;
	private int mDropDownVerticalOffset;
	private boolean mDropDownVerticalOffsetSet;

	private boolean mDropDownAlwaysVisible = false;
	private boolean mForceIgnoreOutsideTouch = false;
	int mListItemExpandMaximum = Integer.MAX_VALUE;

	private View mPromptView;
	private int mPromptPosition = POSITION_PROMPT_ABOVE;

	private DataSetObserver mObserver;

	private View mDropDownAnchorView;

	private Drawable mDropDownListHighlight;

	private AdapterView.OnItemClickListener mItemClickListener;
	private AdapterView.OnItemSelectedListener mItemSelectedListener;

	private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
	private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
	// private final PopupScrollListener mScrollListener = new
	// PopupScrollListener();
	private final ListSelectorHider mHideSelector = new ListSelectorHider();
	private Runnable mShowDropDownRunnable;

	private final Handler mHandler = new Handler();

	private final Rect mTempRect = new Rect();

	private boolean mModal;

	// from ICS ListView
	static final int NO_POSITION = -1;

	/**
	 * Create a new, empty popup window capable of displaying items from a
	 * ListAdapter. Backgrounds should be set using
	 * {@link #setBackgroundDrawable(Drawable)}.
	 * 
	 * @param context Context used for contained views.
	 */
	public ListPopupWindowCompat(final Context context) {
		this(context, null, 0, 0);
	}

	/**
	 * Create a new, empty popup window capable of displaying items from a
	 * ListAdapter. Backgrounds should be set using
	 * {@link #setBackgroundDrawable(Drawable)}.
	 * 
	 * @param context Context used for contained views.
	 * @param attrs Attributes from inflating parent views used to style the
	 *            popup.
	 */
	public ListPopupWindowCompat(final Context context, final AttributeSet attrs) {
		this(context, attrs, 0, 0);
	}

	/**
	 * Create a new, empty popup window capable of displaying items from a
	 * ListAdapter. Backgrounds should be set using
	 * {@link #setBackgroundDrawable(Drawable)}.
	 * 
	 * @param context Context used for contained views.
	 * @param attrs Attributes from inflating parent views used to style the
	 *            popup.
	 * @param defStyleAttr Default style attribute to use for popup content.
	 */
	public ListPopupWindowCompat(final Context context, final AttributeSet attrs, final int defStyleAttr) {
		this(context, attrs, defStyleAttr, 0);
	}

	/**
	 * Create a new, empty popup window capable of displaying items from a
	 * ListAdapter. Backgrounds should be set using
	 * {@link #setBackgroundDrawable(Drawable)}.
	 * 
	 * @param context Context used for contained views.
	 * @param attrs Attributes from inflating parent views used to style the
	 *            popup.
	 * @param defStyleAttr Style attribute to read for default styling of popup
	 *            content.
	 * @param defStyleRes Style resource ID to use for default styling of popup
	 *            content.
	 */
	public ListPopupWindowCompat(final Context context, final AttributeSet attrs, final int defStyleAttr,
			final int defStyleRes) {
		mContext = context;
		mPopup = new PopupWindow(context, attrs);
		mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);

		final Theme theme = context.getTheme();
		final TypedArray array = theme.obtainStyledAttributes(new int[] { R.attr.popupBackground,
				android.R.attr.listSelector });
		mPopup.setBackgroundDrawable(array.getDrawable(0));
		mDropDownListHighlight = array.getDrawable(1);
		array.recycle();
	}

	/**
	 * Clear any current list selection. Only valid when {@link #isShowing()} ==
	 * {@code true}.
	 */
	@Override
	public void clearListSelection() {
		final DropDownListView list = mDropDownList;
		if (list != null) {
			// WARNING: Please read the comment where mListSelectionHidden is
			// declared
			list.mListSelectionHidden = true;
			// list.hideSelector();
			list.requestLayout();
		}
	}

	/**
	 * Dismiss the popup window.
	 */
	@Override
	public void dismiss() {
		mPopup.dismiss();
		removePromptView();
		mPopup.setContentView(null);
		mDropDownList = null;
		mHandler.removeCallbacks(mResizePopupRunnable);
	}

	/**
	 * Returns the view that will be used to anchor this popup.
	 * 
	 * @return The popup's anchor view
	 */
	@Override
	public View getAnchorView() {
		return mDropDownAnchorView;
	}

	/**
	 * Returns the animation style that will be used when the popup window is
	 * shown or dismissed.
	 * 
	 * @return Animation style that will be used.
	 */
	@Override
	public int getAnimationStyle() {
		return mPopup.getAnimationStyle();
	}

	/**
	 * @return The background drawable for the popup window.
	 */
	@Override
	public Drawable getBackground() {
		return mPopup.getBackground();
	}

	/**
	 * @return The height of the popup window in pixels.
	 */
	@Override
	public int getHeight() {
		return mDropDownHeight;
	}

	/**
	 * @return The horizontal offset of the popup from its anchor in pixels.
	 */
	@Override
	public int getHorizontalOffset() {
		return mDropDownHorizontalOffset;
	}

	/**
	 * Return the current value in {@link #setInputMethodMode(int)}.
	 * 
	 * @see #setInputMethodMode(int)
	 */
	@Override
	public int getInputMethodMode() {
		return mPopup.getInputMethodMode();
	}

	/**
	 * @return The {@link ListView} displayed within the popup window. Only
	 *         valid when {@link #isShowing()} == {@code true}.
	 */
	@Override
	public ListView getListView() {
		return mDropDownList;
	}

	/**
	 * @return Where the optional prompt view should appear.
	 * 
	 * @see #POSITION_PROMPT_ABOVE
	 * @see #POSITION_PROMPT_BELOW
	 */
	@Override
	public int getPromptPosition() {
		return mPromptPosition;
	}

	/**
	 * @return The currently selected item or null if the popup is not showing.
	 */
	@Override
	public Object getSelectedItem() {
		if (!isShowing()) return null;
		return mDropDownList.getSelectedItem();
	}

	/**
	 * @return The ID of the currently selected item or
	 *         {@link ListView#INVALID_ROW_ID} if {@link #isShowing()} ==
	 *         {@code false}.
	 * 
	 * @see ListView#getSelectedItemId()
	 */
	@Override
	public long getSelectedItemId() {
		if (!isShowing()) return ListView.INVALID_ROW_ID;
		return mDropDownList.getSelectedItemId();
	}

	/**
	 * @return The position of the currently selected item or
	 *         {@link ListView#INVALID_POSITION} if {@link #isShowing()} ==
	 *         {@code false}.
	 * 
	 * @see ListView#getSelectedItemPosition()
	 */
	@Override
	public int getSelectedItemPosition() {
		if (!isShowing()) return ListView.INVALID_POSITION;
		return mDropDownList.getSelectedItemPosition();
	}

	/**
	 * @return The View for the currently selected item or null if
	 *         {@link #isShowing()} == {@code false}.
	 * 
	 * @see ListView#getSelectedView()
	 */
	@Override
	public View getSelectedView() {
		if (!isShowing()) return null;
		return mDropDownList.getSelectedView();
	}

	/**
	 * Returns the current value in {@link #setSoftInputMode(int)}.
	 * 
	 * @see #setSoftInputMode(int)
	 * @see android.view.WindowManager.LayoutParams#softInputMode
	 */
	@Override
	public int getSoftInputMode() {
		return mPopup.getSoftInputMode();
	}

	/**
	 * @return The vertical offset of the popup from its anchor in pixels.
	 */
	@Override
	public int getVerticalOffset() {
		if (!mDropDownVerticalOffsetSet) return 0;
		return mDropDownVerticalOffset;
	}

	/**
	 * @return The width of the popup window in pixels.
	 */
	@Override
	public int getWidth() {
		return mDropDownWidth;
	}

	/**
	 * @return Whether the drop-down is visible under special conditions.
	 * 
	 * @hide Only used by AutoCompleteTextView under special conditions.
	 */
	public boolean isDropDownAlwaysVisible() {
		return mDropDownAlwaysVisible;
	}

	/**
	 * @return {@code true} if this popup is configured to assume the user does
	 *         not need to interact with the IME while it is showing,
	 *         {@code false} otherwise.
	 */
	@Override
	public boolean isInputMethodNotNeeded() {
		return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
	}

	/**
	 * Returns whether the popup window will be modal when shown.
	 * 
	 * @return {@code true} if the popup window will be modal, {@code false}
	 *         otherwise.
	 */
	@Override
	public boolean isModal() {
		return mModal;
	}

	/**
	 * @return {@code true} if the popup is currently showing, {@code false}
	 *         otherwise.
	 */
	@Override
	public boolean isShowing() {
		return mPopup.isShowing();
	}

	/**
	 * Filter key down events. By forwarding key down events to this function,
	 * views using non-modal ListPopupWindow can have it handle key selection of
	 * items.
	 * 
	 * @param keyCode keyCode param passed to the host view's onKeyDown
	 * @param event event param passed to the host view's onKeyDown
	 * @return true if the event was handled, false if it was ignored.
	 * 
	 * @see #setModal(boolean)
	 */
	public boolean onKeyDown(final int keyCode, final KeyEvent event) {
		// when the drop down is shown, we drive it directly
		if (isShowing()) {
			// the key events are forwarded to the list in the drop down view
			// note that ListView handles space but we don't want that to happen
			// also if selection is not currently in the drop down, then don't
			// let center or enter presses go there since that would cause it
			// to select one of its items
			if (keyCode != KeyEvent.KEYCODE_SPACE
					&& (mDropDownList.getSelectedItemPosition() >= 0 || keyCode != KeyEvent.KEYCODE_ENTER
							&& keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
				final int curIndex = mDropDownList.getSelectedItemPosition();
				boolean consumed;

				final boolean below = !mPopup.isAboveAnchor();

				final ListAdapter adapter = mAdapter;

				int firstItem = Integer.MAX_VALUE;
				int lastItem = Integer.MIN_VALUE;

				if (adapter != null) {
					firstItem = 0;
					lastItem = adapter.getCount() - 1;
					// firstItem = allEnabled ? 0 :
					// mDropDownList.lookForSelectablePosition(0, true);
					// lastItem = allEnabled ? adapter.getCount() - 1 :
					// mDropDownList.lookForSelectablePosition(adapter.getCount()
					// - 1, false);
				}

				if (below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem || !below
						&& keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem) {
					// When the selection is at the top, we block the key
					// event to prevent focus from moving.
					clearListSelection();
					mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
					show();
					return true;
				} else {
					// WARNING: Please read the comment where
					// mListSelectionHidden
					// is declared
					mDropDownList.mListSelectionHidden = false;
				}

				consumed = mDropDownList.onKeyDown(keyCode, event);
				if (DEBUG) {
					Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
				}

				if (consumed) {
					// If it handled the key event, then the user is
					// navigating in the list, so we should put it in front.
					mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
					// Here's a little trick we need to do to make sure that
					// the list view is actually showing its focus indicator,
					// by ensuring it has focus and getting its window out
					// of touch mode.
					mDropDownList.requestFocusFromTouch();
					show();

					switch (keyCode) {
					// avoid passing the focus from the text view to the
					// next component
						case KeyEvent.KEYCODE_ENTER:
						case KeyEvent.KEYCODE_DPAD_CENTER:
						case KeyEvent.KEYCODE_DPAD_DOWN:
						case KeyEvent.KEYCODE_DPAD_UP:
							return true;
					}
				} else {
					if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
						// when the selection is at the bottom, we block the
						// event to avoid going to the next focusable widget
						if (curIndex == lastItem) return true;
					} else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex == firstItem) return true;
				}
			}
		}

		return false;
	}

	/**
	 * Filter pre-IME key events. By forwarding
	 * {@link View#onKeyPreIme(int, KeyEvent)} events to this function, views
	 * using ListPopupWindow can have it dismiss the popup when the back key is
	 * pressed.
	 * 
	 * @param keyCode keyCode param passed to the host view's onKeyPreIme
	 * @param event event param passed to the host view's onKeyPreIme
	 * @return true if the event was handled, false if it was ignored.
	 * 
	 * @see #setModal(boolean)
	 */
	public boolean onKeyPreIme(final int keyCode, final KeyEvent event) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR)
			return OnKeyPreImeAccessor.onKeyPreIme(this, keyCode, event);
		return false;
	}

	/**
	 * Filter key down events. By forwarding key up events to this function,
	 * views using non-modal ListPopupWindow can have it handle key selection of
	 * items.
	 * 
	 * @param keyCode keyCode param passed to the host view's onKeyUp
	 * @param event event param passed to the host view's onKeyUp
	 * @return true if the event was handled, false if it was ignored.
	 * 
	 * @see #setModal(boolean)
	 */
	public boolean onKeyUp(final int keyCode, final KeyEvent event) {
		if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
			final boolean consumed = mDropDownList.onKeyUp(keyCode, event);
			if (consumed) {
				switch (keyCode) {
				// if the list accepts the key events and the key event
				// was a click, the text view gets the selected item
				// from the drop down as its content
					case KeyEvent.KEYCODE_ENTER:
					case KeyEvent.KEYCODE_DPAD_CENTER:
						dismiss();
						break;
				}
			}
			return consumed;
		}
		return false;
	}

	/**
	 * Perform an item click operation on the specified list adapter position.
	 * 
	 * @param position Adapter position for performing the click
	 * @return true if the click action could be performed, false if not. (e.g.
	 *         if the popup was not showing, this method would return false.)
	 */
	@Override
	public boolean performItemClick(final int position) {
		if (isShowing()) {
			if (mItemClickListener != null) {
				final DropDownListView list = mDropDownList;
				final View child = list.getChildAt(position - list.getFirstVisiblePosition());
				final ListAdapter adapter = list.getAdapter();
				mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
			}
			return true;
		}
		return false;
	}

	/**
	 * Post a {@link #show()} call to the UI thread.
	 */
	@Override
	public void postShow() {
		mHandler.post(mShowDropDownRunnable);
	}

	/**
	 * Sets the adapter that provides the data and the views to represent the
	 * data in this popup window.
	 * 
	 * @param adapter The adapter to use to create this window's content.
	 */
	@Override
	public void setAdapter(final ListAdapter adapter) {
		if (mObserver == null) {
			mObserver = new PopupDataSetObserver();
		} else if (mAdapter != null) {
			mAdapter.unregisterDataSetObserver(mObserver);
		}
		mAdapter = adapter;
		if (mAdapter != null) {
			adapter.registerDataSetObserver(mObserver);
		}

		if (mDropDownList != null) {
			mDropDownList.setAdapter(mAdapter);
		}
	}

	/**
	 * Sets the popup's anchor view. This popup will always be positioned
	 * relative to the anchor view when shown.
	 * 
	 * @param anchor The view to use as an anchor.
	 */
	@Override
	public void setAnchorView(final View anchor) {
		mDropDownAnchorView = anchor;
	}

	/**
	 * Set an animation style to use when the popup window is shown or
	 * dismissed.
	 * 
	 * @param animationStyle Animation style to use.
	 */
	@Override
	public void setAnimationStyle(final int animationStyle) {
		mPopup.setAnimationStyle(animationStyle);
	}

	/**
	 * Sets a drawable to be the background for the popup window.
	 * 
	 * @param d A drawable to set as the background.
	 */
	@Override
	public void setBackgroundDrawable(final Drawable d) {
		mPopup.setBackgroundDrawable(d);
	}

	/**
	 * Sets the width of the popup window by the size of its content. The final
	 * width may be larger to accommodate styled window dressing.
	 * 
	 * @param width Desired width of content in pixels.
	 */
	@Override
	public void setContentWidth(final int width) {
		final Drawable popupBackground = mPopup.getBackground();
		if (popupBackground != null) {
			popupBackground.getPadding(mTempRect);
			mDropDownWidth = mTempRect.left + mTempRect.right + width;
		} else {
			setWidth(width);
		}
	}

	/**
	 * Sets whether the drop-down should remain visible under certain
	 * conditions.
	 * 
	 * The drop-down will occupy the entire screen below {@link #getAnchorView}
	 * regardless of the size or content of the list. {@link #getBackground()}
	 * will fill any space that is not used by the list.
	 * 
	 * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
	 * 
	 * @hide Only used by AutoCompleteTextView under special conditions.
	 */
	public void setDropDownAlwaysVisible(final boolean dropDownAlwaysVisible) {
		mDropDownAlwaysVisible = dropDownAlwaysVisible;
	}

	/**
	 * Forces outside touches to be ignored. Normally if
	 * {@link #isDropDownAlwaysVisible()} is false, we allow outside touch to
	 * dismiss the dropdown. If this is set to true, then we ignore outside
	 * touch even when the drop down is not set to always visible.
	 * 
	 * @hide Used only by AutoCompleteTextView to handle some internal special
	 *       cases.
	 */
	public void setForceIgnoreOutsideTouch(final boolean forceIgnoreOutsideTouch) {
		mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
	}

	/**
	 * Sets the height of the popup window in pixels. Can also be
	 * {@link #MATCH_PARENT}.
	 * 
	 * @param height Height of the popup window.
	 */
	@Override
	public void setHeight(final int height) {
		mDropDownHeight = height;
	}

	/**
	 * Set the horizontal offset of this popup from its anchor view in pixels.
	 * 
	 * @param offset The horizontal offset of the popup from its anchor.
	 */
	@Override
	public void setHorizontalOffset(final int offset) {
		mDropDownHorizontalOffset = offset;
	}

	/**
	 * Control how the popup operates with an input method: one of
	 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, or
	 * {@link #INPUT_METHOD_NOT_NEEDED}.
	 * 
	 * <p>
	 * If the popup is showing, calling this method will take effect only the
	 * next time the popup is shown or through a manual call to the
	 * {@link #show()} method.
	 * </p>
	 * 
	 * @see #getInputMethodMode()
	 * @see #show()
	 */
	@Override
	public void setInputMethodMode(final int mode) {
		mPopup.setInputMethodMode(mode);
	}

	/**
	 * Sets a drawable to use as the list item selector.
	 * 
	 * @param selector List selector drawable to use in the popup.
	 */
	@Override
	public void setListSelector(final Drawable selector) {
		mDropDownListHighlight = selector;
	}

	/**
	 * Set whether this window should be modal when shown.
	 * 
	 * <p>
	 * If a popup window is modal, it will receive all touch and key input. If
	 * the user touches outside the popup window's content area the popup window
	 * will be dismissed.
	 * 
	 * @param modal {@code true} if the popup window should be modal,
	 *            {@code false} otherwise.
	 */
	@Override
	public void setModal(final boolean modal) {
		mModal = true;
		mPopup.setFocusable(modal);
	}

	/**
	 * Set a listener to receive a callback when the popup is dismissed.
	 * 
	 * @param listener Listener that will be notified when the popup is
	 *            dismissed.
	 */
	@Override
	public void setOnDismissListener(final PopupWindow.OnDismissListener listener) {
		mPopup.setOnDismissListener(listener);
	}

	/**
	 * Sets a listener to receive events when a list item is clicked.
	 * 
	 * @param clickListener Listener to register
	 * 
	 * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
	 */
	@Override
	public void setOnItemClickListener(final AdapterView.OnItemClickListener clickListener) {
		mItemClickListener = clickListener;
	}

	/**
	 * Sets a listener to receive events when a list item is selected.
	 * 
	 * @param selectedListener Listener to register.
	 * 
	 * @see ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
	 */
	@Override
	public void setOnItemSelectedListener(final AdapterView.OnItemSelectedListener selectedListener) {
		mItemSelectedListener = selectedListener;
	}

	/**
	 * Set where the optional prompt view should appear. The default is
	 * {@link #POSITION_PROMPT_ABOVE}.
	 * 
	 * @param position A position constant declaring where the prompt should be
	 *            displayed.
	 * 
	 * @see #POSITION_PROMPT_ABOVE
	 * @see #POSITION_PROMPT_BELOW
	 */
	@Override
	public void setPromptPosition(final int position) {
		mPromptPosition = position;
	}

	/**
	 * Set a view to act as a user prompt for this popup window. Where the
	 * prompt view will appear is controlled by {@link #setPromptPosition(int)}.
	 * 
	 * @param prompt View to use as an informational prompt.
	 */
	@Override
	public void setPromptView(final View prompt) {
		final boolean showing = isShowing();
		if (showing) {
			removePromptView();
		}
		mPromptView = prompt;
		if (showing) {
			show();
		}
	}

	/**
	 * Set the selected position of the list. Only valid when
	 * {@link #isShowing()} == {@code true}.
	 * 
	 * @param position List position to set as selected.
	 */
	@Override
	public void setSelection(final int position) {
		final DropDownListView list = mDropDownList;
		if (isShowing() && list != null) {
			list.mListSelectionHidden = false;
			list.setSelection(position);
			if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
				list.setItemChecked(position, true);
			}
		}
	}

	/**
	 * Sets the operating mode for the soft input area.
	 * 
	 * @param mode The desired mode, see
	 *            {@link android.view.WindowManager.LayoutParams#softInputMode}
	 *            for the full list
	 * 
	 * @see android.view.WindowManager.LayoutParams#softInputMode
	 * @see #getSoftInputMode()
	 */
	@Override
	public void setSoftInputMode(final int mode) {
		mPopup.setSoftInputMode(mode);
	}

	/**
	 * Set the vertical offset of this popup from its anchor view in pixels.
	 * 
	 * @param offset The vertical offset of the popup from its anchor.
	 */
	@Override
	public void setVerticalOffset(final int offset) {
		mDropDownVerticalOffset = offset;
		mDropDownVerticalOffsetSet = true;
	}

	/**
	 * Sets the width of the popup window in pixels. Can also be
	 * {@link #MATCH_PARENT} or {@link #WRAP_CONTENT}.
	 * 
	 * @param width Width of the popup window.
	 */
	@Override
	public void setWidth(final int width) {
		mDropDownWidth = width;
	}

	/**
	 * Show the popup list. If the list is already showing, this method will
	 * recalculate the popup's size and position.
	 */
	@Override
	public void show() {
		final int height = buildDropDown();

		int widthSpec = 0;
		int heightSpec = 0;

		final boolean noInputMethod = isInputMethodNotNeeded();
		// mPopup.setAllowScrollingAnchorParent(!noInputMethod);

		if (mPopup.isShowing()) {
			if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
				// The call to PopupWindow's update method below can accept -1
				// for any
				// value you do not want to update.
				widthSpec = -1;
			} else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
				widthSpec = getAnchorView().getWidth();
			} else {
				widthSpec = mDropDownWidth;
			}

			if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
				// The call to PopupWindow's update method below can accept -1
				// for any
				// value you do not want to update.
				heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
				if (noInputMethod) {
					mPopup.setWindowLayoutMode(
							mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? ViewGroup.LayoutParams.MATCH_PARENT
									: 0, 0);
				} else {
					mPopup.setWindowLayoutMode(
							mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? ViewGroup.LayoutParams.MATCH_PARENT
									: 0, ViewGroup.LayoutParams.MATCH_PARENT);
				}
			} else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
				heightSpec = height;
			} else {
				heightSpec = mDropDownHeight;
			}

			mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);

			mPopup.update(getAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, widthSpec, heightSpec);
		} else {
			if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
				widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
			} else {
				if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
					mPopup.setWidth(getAnchorView().getWidth());
				} else {
					mPopup.setWidth(mDropDownWidth);
				}
			}

			if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
				heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
			} else {
				if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
					mPopup.setHeight(height);
				} else {
					mPopup.setHeight(mDropDownHeight);
				}
			}

			mPopup.setWindowLayoutMode(widthSpec, heightSpec);
			// mPopup.setClipToScreenEnabled(true); might be important aap

			// use outside touchable to dismiss drop down when touching outside
			// of it, so
			// only set this if the dropdown is not always visible
			mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
			mPopup.setTouchInterceptor(mTouchInterceptor);
			mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset);
			mDropDownList.setSelection(ListView.INVALID_POSITION);

			if (!mModal || mDropDownList.isInTouchMode()) {
				clearListSelection();
			}
			if (!mModal) {
				mHandler.post(mHideSelector);
			}
		}
	}

	/**
	 * <p>
	 * Builds the popup window's content and returns the height the popup should
	 * have. Returns -1 when the content already exists.
	 * </p>
	 * 
	 * @return the content's height or -1 if content already exists
	 */
	private int buildDropDown() {
		ViewGroup dropDownView;
		int otherHeights = 0;

		if (mDropDownList == null) {
			final Context context = mContext;

			/**
			 * This Runnable exists for the sole purpose of checking if the view
			 * layout has got completed and if so call showDropDown to display
			 * the drop down. This is used to show the drop down as soon as
			 * possible after user opens up the search dialog, without waiting
			 * for the normal UI pipeline to do it's job which is slower than
			 * this method.
			 */
			mShowDropDownRunnable = new Runnable() {
				@Override
				public void run() {
					// View layout should be all done before displaying the drop
					// down.
					final View view = getAnchorView();
					if (view != null && view.getWindowToken() != null) {
						show();
					}
				}
			};

			mDropDownList = new DropDownListView(context, !mModal);
			if (mDropDownListHighlight != null) {
				mDropDownList.setSelector(mDropDownListHighlight);
			}
			mDropDownList.setAdapter(mAdapter);
			mDropDownList.setOnItemClickListener(mItemClickListener);
			mDropDownList.setFocusable(true);
			mDropDownList.setFocusableInTouchMode(true);
			mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
				@Override
				public void onItemSelected(final AdapterView<?> parent, final View view, final int position,
						final long id) {

					if (position != -1) {
						final DropDownListView dropDownList = mDropDownList;

						if (dropDownList != null) {
							dropDownList.mListSelectionHidden = false;
						}
					}
				}

				@Override
				public void onNothingSelected(final AdapterView<?> parent) {
				}
			});
			// mDropDownList.setOnScrollListener(mScrollListener);

			if (mItemSelectedListener != null) {
				mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
			}

			dropDownView = mDropDownList;

			final View hintView = mPromptView;
			if (hintView != null) {
				// if an hint has been specified, we accomodate more space for
				// it and
				// add a text view in the drop down menu, at the bottom of the
				// list
				final LinearLayout hintContainer = new LinearLayout(context);
				hintContainer.setOrientation(LinearLayout.VERTICAL);

				LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
						ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f);

				switch (mPromptPosition) {
					case POSITION_PROMPT_BELOW:
						hintContainer.addView(dropDownView, hintParams);
						hintContainer.addView(hintView);
						break;

					case POSITION_PROMPT_ABOVE:
						hintContainer.addView(hintView);
						hintContainer.addView(dropDownView, hintParams);
						break;

					default:
						Log.e(TAG, "Invalid hint position " + mPromptPosition);
						break;
				}

				// measure the hint's height to find how much more vertical
				// space
				// we need to add to the drop down's height
				final int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST);
				final int heightSpec = MeasureSpec.UNSPECIFIED;
				hintView.measure(widthSpec, heightSpec);

				hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
				otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin + hintParams.bottomMargin;

				dropDownView = hintContainer;
			}

			mPopup.setContentView(dropDownView);
		} else {
			dropDownView = (ViewGroup) mPopup.getContentView();
			final View view = mPromptView;
			if (view != null) {
				final LinearLayout.LayoutParams hintParams = (LinearLayout.LayoutParams) view.getLayoutParams();
				otherHeights = view.getMeasuredHeight() + hintParams.topMargin + hintParams.bottomMargin;
			}
		}

		// getMaxAvailableHeight() subtracts the padding, so we put it back
		// to get the available height for the whole window
		int padding = 0;
		final Drawable background = mPopup.getBackground();
		if (background != null) {
			background.getPadding(mTempRect);
			padding = mTempRect.top + mTempRect.bottom;

			// If we don't have an explicit vertical offset, determine one from
			// the window
			// background so that content will line up.
			if (!mDropDownVerticalOffsetSet) {
				mDropDownVerticalOffset = -mTempRect.top;
			}
		}

		final int maxHeight = mPopup.getMaxAvailableHeight(getAnchorView(), mDropDownVerticalOffset);

		if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT)
			return maxHeight + padding;

		final int listContent = measureHeightOfChildren(MeasureSpec.UNSPECIFIED, 0, NO_POSITION, maxHeight
				- otherHeights, -1);
		// add padding only if the list has items in it, that way we don't show
		// the popup if it is not needed
		if (listContent > 0) {
			otherHeights += padding;
		}

		return listContent + otherHeights;
	}

	private void measureScrapChild(final View child, final int position, final int widthMeasureSpec, final Rect padding) {
		LayoutParams p = (LayoutParams) child.getLayoutParams();
		if (p == null) {
			p = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0);
			child.setLayoutParams(p);
		}
		// p.viewType = mAdapter.getItemViewType(position);
		// p.forceAdd = true;

		final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec, padding.left + padding.right,
				p.width);
		final int lpHeight = p.height;
		int childHeightSpec;
		if (lpHeight > 0) {
			childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
		} else {
			childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
		}
		child.measure(childWidthSpec, childHeightSpec);
	}

	private void removePromptView() {
		if (mPromptView != null) {
			final ViewParent parent = mPromptView.getParent();
			if (parent instanceof ViewGroup) {
				final ViewGroup group = (ViewGroup) parent;
				group.removeView(mPromptView);
			}
		}
	}

	/**
	 * This method borrowed from ICS ListView.
	 * 
	 * Measures the height of the given range of children (inclusive) and
	 * returns the height with this ListView's padding and divider heights
	 * included. If maxHeight is provided, the measuring will stop when the
	 * current height reaches maxHeight.
	 * 
	 * @param widthMeasureSpec The width measure spec to be given to a child's
	 *            {@link View#measure(int, int)}.
	 * @param startPosition The position of the first child to be shown.
	 * @param endPosition The (inclusive) position of the last child to be
	 *            shown. Specify {@link #NO_POSITION} if the last child should
	 *            be the last available child from the adapter.
	 * @param maxHeight The maximum height that will be returned (if all the
	 *            children don't fit in this value, this value will be
	 *            returned).
	 * @param disallowPartialChildPosition In general, whether the returned
	 *            height should only contain entire children. This is more
	 *            powerful--it is the first inclusive position at which partial
	 *            children will not be allowed. Example: it looks nice to have
	 *            at least 3 completely visible children, and in portrait this
	 *            will most likely fit; but in landscape there could be times
	 *            when even 2 children can not be completely shown, so a value
	 *            of 2 (remember, inclusive) would be good (assuming
	 *            startPosition is 0).
	 * @return The height of this ListView with the given children.
	 */
	final int measureHeightOfChildren(final int widthMeasureSpec, final int startPosition, int endPosition,
			final int maxHeight, final int disallowPartialChildPosition) {

		final Rect padding = new Rect();
		// not sure if this is the right padding to use...
		mPopup.getBackground().getPadding(padding);

		final ListAdapter adapter = mAdapter;
		if (adapter == null) return padding.top + padding.bottom;

		// Include the padding of the list
		int returnedHeight = padding.top + padding.bottom;
		final int dividerHeight = 0; // ((mDividerHeight > 0) && mDivider !=
										// null) ? mDividerHeight : 0;
		// The previous height value that was less than maxHeight and contained
		// no partial children
		int prevHeightWithoutPartialChild = 0;
		int i;
		View child;

		// mItemCount - 1 since endPosition parameter is inclusive
		endPosition = endPosition == NO_POSITION ? adapter.getCount() - 1 : endPosition;
		// final AbsListView.RecycleBin recycleBin = mRecycler;
		// final boolean recyle = recycleOnMeasure();
		// final boolean[] isScrap = mIsScrap;

		for (i = startPosition; i <= endPosition; ++i) {
			// child = mDropDownList.obtainView(i);

			child = mAdapter.getView(i, null, mDropDownList);

			measureScrapChild(child, i, widthMeasureSpec, padding);

			if (i > 0) {
				// Count the divider for all but one child
				returnedHeight += dividerHeight;
			}

			// Recycle the view before we possibly return from the method
			// if (recyle && recycleBin.shouldRecycleViewType(
			// ((LayoutParams) child.getLayoutParams()).viewType)) {
			// recycleBin.addScrapView(child, -1);
			// }

			returnedHeight += child.getMeasuredHeight();

			if (returnedHeight >= maxHeight) // We went over, figure out which
												// height to return. If
				// returnedHeight > maxHeight,
				// then the i'th position did not fit completely.
				return disallowPartialChildPosition >= 0 // Disallowing is
															// enabled (> -1)
						&& i > disallowPartialChildPosition // We've past the
															// min pos
						&& prevHeightWithoutPartialChild > 0 // We have a prev
																// height
						&& returnedHeight != maxHeight // i'th child did not
														// fit completely
				? prevHeightWithoutPartialChild : maxHeight;

			if (disallowPartialChildPosition >= 0 && i >= disallowPartialChildPosition) {
				prevHeightWithoutPartialChild = returnedHeight;
			}
		}

		// At this point, we went through the range of children, and they each
		// completely fit, so return the returnedHeight
		return returnedHeight;
	}

	/**
	 * The maximum number of list items that can be visible and still have the
	 * list expand when touched.
	 * 
	 * @param max Max number of items that can be visible and still allow the
	 *            list to expand.
	 */
	void setListItemExpandMax(final int max) {
		mListItemExpandMaximum = max;
	}

	/**
	 * <p>
	 * Wrapper class for a ListView. This wrapper can hijack the focus to make
	 * sure the list uses the appropriate drawables and states when displayed on
	 * screen within a drop down. The focus is never actually passed to the drop
	 * down in this mode; the list only looks focused.
	 * </p>
	 */
	private static class DropDownListView extends ListView {
		/*
		 * WARNING: This is a workaround for a touch mode issue.
		 * 
		 * Touch mode is propagated lazily to windows. This causes problems in
		 * the following scenario: - Type something in the AutoCompleteTextView
		 * and get some results - Move down with the d-pad to select an item in
		 * the list - Move up with the d-pad until the selection disappears -
		 * Type more text in the AutoCompleteTextView *using the soft keyboard*
		 * and get new results; you are now in touch mode - The selection comes
		 * back on the first item in the list, even though the list is supposed
		 * to be in touch mode
		 * 
		 * Using the soft keyboard triggers the touch mode change but that
		 * change is propagated to our window only after the first list layout,
		 * therefore after the list attempts to resurrect the selection.
		 * 
		 * The trick to work around this issue is to pretend the list is in
		 * touch mode when we know that the selection should not appear, that is
		 * when we know the user moved the selection away from the list.
		 * 
		 * This boolean is set to true whenever we explicitly hide the list's
		 * selection and reset to false whenever we know the user moved the
		 * selection back to the list.
		 * 
		 * When this boolean is true, isInTouchMode() returns true, otherwise it
		 * returns super.isInTouchMode().
		 */
		private boolean mListSelectionHidden;

		/**
		 * True if this wrapper should fake focus.
		 */
		private final boolean mHijackFocus;

		/**
		 * <p>
		 * Creates a new list view wrapper.
		 * </p>
		 * 
		 * @param context this view's context
		 */
		public DropDownListView(final Context context, final boolean hijackFocus) {
			super(context, null, android.R.attr.dropDownListViewStyle);
			mHijackFocus = hijackFocus;
			// setCacheColorHint(0);
			setPadding(0, 0, 0, 0);
		}

		/**
		 * <p>
		 * Returns the focus state in the drop down.
		 * </p>
		 * 
		 * @return true always if hijacking focus
		 */
		@Override
		public boolean hasFocus() {
			return mHijackFocus || super.hasFocus();
		}

		/**
		 * <p>
		 * Returns the focus state in the drop down.
		 * </p>
		 * 
		 * @return true always if hijacking focus
		 */
		@Override
		public boolean hasWindowFocus() {
			return mHijackFocus || super.hasWindowFocus();
		}

		/**
		 * <p>
		 * Returns the focus state in the drop down.
		 * </p>
		 * 
		 * @return true always if hijacking focus
		 */
		@Override
		public boolean isFocused() {
			return mHijackFocus || super.isFocused();
		}

		@Override
		public boolean isInTouchMode() {
			// WARNING: Please read the comment where mListSelectionHidden is
			// declared
			return mHijackFocus && mListSelectionHidden || super.isInTouchMode();
		}

	}

	private class ListSelectorHider implements Runnable {
		@Override
		public void run() {
			clearListSelection();
		}
	}

	private class PopupDataSetObserver extends DataSetObserver {
		@Override
		public void onChanged() {
			if (isShowing()) {
				// Resize the popup to fit new content
				show();
			}
		}

		@Override
		public void onInvalidated() {
			dismiss();
		}
	}

	private class PopupTouchInterceptor implements OnTouchListener {
		@Override
		public boolean onTouch(final View v, final MotionEvent event) {
			final int action = event.getAction();
			final int x = (int) event.getX();
			final int y = (int) event.getY();

			if (action == MotionEvent.ACTION_DOWN && mPopup != null && mPopup.isShowing() && x >= 0
					&& x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight()) {
				mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
			} else if (action == MotionEvent.ACTION_UP) {
				mHandler.removeCallbacks(mResizePopupRunnable);
			}
			return false;
		}
	}

	private class ResizePopupRunnable implements Runnable {
		@Override
		public void run() {
			if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount()
					&& mDropDownList.getChildCount() <= mListItemExpandMaximum) {
				mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
				show();
			}
		}
	}

	@SuppressLint("NewApi")
	static class OnKeyPreImeAccessor {
		public static boolean onKeyPreIme(final ListPopupWindowCompat window, final int keyCode, final KeyEvent event) {
			if (keyCode == KeyEvent.KEYCODE_BACK && window.isShowing()) {
				// special case for the back key, we do not even try to send it
				// to the drop down list but instead, consume it immediately
				final View anchorView = window.mDropDownAnchorView;
				if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
					final KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
					if (state != null) {
						state.startTracking(event, window);
					}
					return true;
				} else if (event.getAction() == KeyEvent.ACTION_UP) {
					final KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
					if (state != null) {
						state.handleUpEvent(event);
					}
					if (event.isTracking() && !event.isCanceled()) {
						window.dismiss();
						return true;
					}
				}
			}
			return false;
		}
	}

}
